补注喵
for Proj-01
关于虚拟环境
虚拟环境是什么?
是一种用于隔离项目依赖关系的工具。每个虚拟环境都有自己的解释器和库。
通常虚拟环境中的解释器会指向全局环境中的解释器,也就是说在爆改全局环境(主要是更改解释器位置)后别忘了重建你的虚拟环境。
为什么需要虚拟环境
主要是能够避免不同项目的依赖的冲突。比如有一个项目需求低版本的 numpy
但是另一个需求高版本,把它们放在同一个环境中尝试安装依赖时就会出问题。
手动创建虚拟环境
通过 VSCode 创建详见正文。这里尝试讲解手动使用内置模块 venv
进行创建。
在项目文件夹中打开终端,使用下面的指令创建虚拟环境。
python -m venv ./.venv
大概就是调用 Python 的一个叫 venv
的模块,在当前目录下的 .venv
文件夹下创建虚拟环境。
将虚拟环境所在文件夹命名为
.venv
是一个约定俗成的做法,很多开发工具的默认行为就是这样,它们会自动识别并使用这个文件夹中的虚拟环境。
稍等一下就能完成。很快地,项目文件夹下多出了一个叫 .venv
的文件夹。
在终端下执行 ./.venv/Scripts/activate
就能手动激活虚拟环境。
若遇到
powershell
阻止,按照提示进行设置即可。
要检查是否真正处于虚拟环境,使用 powershell 指令 Get-Command
查找 python
解释器的位置,判断一下是否处于正确的位置( .venv/Scripts/python
)就行了。
关于 requests
库
很优雅的一个多线程安全的第三方网络请求库。
不过你也可以尝试只用 Python 标准库实现这个项目,这是可以做到的!
关于语法
Python 很容易入门,语法限制也很少。但这不意味着它很简单,更不意味着它很随便。
—— MelodyEcho
这里仅尝试讲解正文中使用到的。
快去看官方文档
注释
Python 中使用 #
符进行注释,在当前行中非字符串内容的 #
及其后的内容都会被当成注释。
有时能在一些文件看到以 #!
开头的首行注释,这被称作 Shebang。
Shebang 这一语法特性由
#!
开头。在开头字符之后,可以有一个或数个空白字符,后接解释器的绝对路径,用于调用解释器。在直接调用脚本时,调用者会利用 Shebang 提供的信息调用相应的解释器,从而使得脚本文件的调用方式与普通的可执行文件类似。[1]
你说得对 ☝️🤓,但是 '''
,即连续的三个引号包围的内容不是注释,而是多行字符串。它也常被用在 docstring 中。
控制流
导入模块
import
语句用于导入模块。
有一个叫
importlib
的库也可以用来导入模块,而且动态
常用的写法:
# 直接导入
import math
import os.path
# 从某个模块中导入对象
from lxml import etree
# 导入某个模块中的所有对象到当前命名空间中,非必要不使用
from tkinter import *
# 换个名称导入
from tkinter import messagebox as msgbox
# 导入多个对象
from urllib import request, parse
# 上述指定了模块名的叫做绝对导入,从项目的根目录开始导入模块
# 下面的是相对导入,一般用在包的内部文件之间。
from . import utils
from ..module_a import func
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
PEP8 中对于导入语句的写法有推荐,可以看看。
关于机制,Python 会按顺序搜索 sys.path
列表中的每个目录,查找与模块名匹配的文件。
sys.path
默认包括:
- 当前文件所在目录
%PYTHONPATH%
环境变量指定的目录- 标准库目录,通常是
python/Lib
- 安装的第三方包的目录,通常是
python/Lib/site-packages
优先级:当前目录 -> site-packages -> 内置模块
你可以手动为 sys.path
添加路径,让它去额外的目录找模块;也可以自定义模块加载的各个步骤,总之就是自由度十分甚至九分地高。
更多内容参见官方文档,或者去问问 AI。
函数
函数的定义
使用 def
或者 lambda
定义,由后者定义的函数叫做匿名函数。
函数定义是一条可执行语句。它执行时会在当前局部命名空间中将函数名称绑定到一个函数对象(函数可执行代码的包装器)。这个函数对象包含对当前全局命名空间的引用,作为函数被调用时所使用的全局命名空间。(摘自官方文档)
嗯你问函数它本身是什么…… 函数它就是函数啊
Python 中的函数是封装了代码块的对象。使用它可以复用代码,减少重复。
快去看官方文档 ww
一个典型的函数大概长这样:
# 形式参数 参数类型标注(可选)
# vv vvvvvvvvvvv
def join(ss : list[str]) -> str:
# ^^^^---------------- ^^^^^^
# 函数名 形式参数列表 返回值类型标注(可选)
return "".join(ss)
2
3
4
5
6
关于形参与实参
形参是函数在定义的时候写好的参数,实参是函数在被实际调用时传入的参数
为什么要做类型标注 (Type Hints)?
你说得对🤓,虽然 Python 是一种动态类型语言,但是类型标注也有很多好处。它能提升代码的可维护性和可读性,以及便于生成文档。此外在面对稍微复杂一些的代码时单靠 IDE 进行类型推导会显得有些吃力,但要是加上类型标注的话就可以帮助
思维阻滞的IDE 进行自动补全和错误检测等工作。不过它完全是可选的,最终写不写还是取决于你就是了。
关于类型标注的详细讲解,见 Proj-02 的补注。
关于函数的默认参数
不要将默认参数设定为可变对象。作为替代,可以将默认参数设定为 None
,并在函数内部进行处理。
因为函数的默认参数只会在定义时被计算一次,后续每次调用时它们都会被共享。如果默认参数被修改,这些修改会影响到后续的函数调用。它在默认参数被实参覆盖时不会有问题,但在默认参数留空时便会出现意料外的情况。
来看例子。
>>> def append_to_list(value, my_list=[]):
... my_list.append(value)
... return my_list
...
>>> print(append_to_list(1))
[1]
>>> print(append_to_list(2))
[1, 2]
>>> print(append_to_list(3))
[1, 2, 3]
2
3
4
5
6
7
8
9
10
替换为使用 None
:
>>> def append_to_list(value, my_list=None):
... if my_list is None:
... my_list = []
... my_list.append(value)
... return my_list
...
>>> print(append_to_list(1))
[1]
>>> print(append_to_list(2))
[2]
>>> print(append_to_list(3))
[3]
2
3
4
5
6
7
8
9
10
11
12
异常
异常捕获
完全体的异常处理大概像这样:
try:
# 可能会出错的代码
x = 1 / int(input("Your Number:"))
assert 1 == 0, "oops, 1 != 0"
except ZeroDivisionError as e:
... # 发生除零错误时会执行的代码
except AssertionError as e:
... # 发生断言错误时会执行的代码
except Exception as e:
... # 发生一般错误时会执行的代码
else:
... # 没有发生错误时会执行的代码
finally:
... # 不管发生了什么都会执行的代码
2
3
4
5
6
7
8
9
10
11
12
13
14
异常匹配顺序从上到下,匹配成功后便不再继续匹配。若最终都没有匹配上则错误还是会被抛出(但 finally
块仍然有效)
关于裸
except:
等价于
except BaseException:
,可以捕获所有异常,包括系统退出和中断的异常,像SystemExit
、KeyboardInterrupt
和GeneratorExit
。因为经常会捕获到不该捕获的东西,语义不明确所以不建议使用。一般日常建议使用
except Exception:
。就算你真的想捕获系统级异常,也应该使用except BaseException:
进行平替,因为它的语义更明确一些。
...
?没错
...
也是一个对象,叫做Ellipsis
,通常用来表示省略或占位。在某些特殊的场景很有用(比如numpy
中创建多维数组)。
except ... as ...
可以将捕获的异常实例保存到一个变量中,方便后续做处理
异常抛出
使用 raise
语句抛出一个异常对象。
在抛出之前,先要将异常类实例化,在实例化时可以传递具体的错误消息。(不实例化就抛出是 py2 的做法)
raise RuntimeError("A runtime error.")
raise ... from ...
可以从一个已有的异常实例抛出一个新异常,原有异常会被保存在新异常的 __cause__
属性中。常用在 except
块下。
变量作用域与生命周期
Python 中的局部变量的作用域是它直接所在的函数,全局变量的则是它所在的模块。
在一个变量超出它的作用域,且它的引用计数变为 0
时,它就会被删除。
对于循环引用,Python 的垃圾回收器负责清理它们。可以通过调用
gc.collect()
来手动执行垃圾回收。
Python 没有像 C/C++ 或 Java 那样的块级作用域 (Block Scope),在控制结构 (
if
/while
/match
/for
/...) 中声明的变量在控制结构之外依然可以访问,只要是在相同的函数或全局作用域中;也没有像 js 那样的变量提升,在变量被声明并赋值之前访问它会报错。
来点 C 语言笑话:
void just_a_test(void){
// 我 有 异 域 症
{int i;}
{i = 0;}
}
2
3
4
5
引用与拷贝
Python 中的所有数据都是对象。变量名实际上是指向这些对象的引用。当你赋值一个变量时,你只是将这个变量名绑定到某个对象上,并不复制对象本身。
>>> a = [1, 2, 3]
>>> a
[1, 2, 3]
>>> b = a
>>> b.append(4)
>>> a
[1, 2, 3, 4]
2
3
4
5
6
7
要复制对象,可使用
copy
库中的copy()
和deepcopy()
函数,或某些对象内置的copy()
方法
copy()
: 浅拷贝,仅复制顶层结构,产生的新对象内层仍然共享。deepcopy()
: 深拷贝,复制整个对象,产生的新对象完全独立
Python 中的对象分为可变(e.g. 列表 / 字典 / 集合)和不可变(e.g. 整数 / 字符串 / 元组)两种。
对于不可变的对象,对它做修改或赋值会在原地生成一个新的对象,再将这个对象绑定到变量上。这就是平时在数字 / 字符串变量之间进行赋值看起来像拷贝的原因。
>>> a = 1
>>> b = a
>>> b = 2
>>> a
1
>>> b
2
2
3
4
5
6
7
常见的不可变对象包括:
- 整数
int
- 浮点数
float
- 字符串
str
- 元组
tuple
- 布尔值
bool
- 字节串
bytes
- 冻结集合
frozenset
详见官方文档
with 语句
对于带有上下文管理器的对象,可以使用 with
来进行上下文管理。
进入 with
语句块时,对象的 __enter__()
方法会被自动调用;离开时,对象的 __exit__()
方法也会被自动调用,无论出错与否。保证能够正确关闭句柄、结束会话等。
with open("./result.txt", "w+", encoding="utf-8") as fp:
fp.write("Pretending to be data")
2
requests
中的请求也可以用噢,看看本节的代码?
格式化字符串
格式化字符串是指将变量或表达式的值嵌入到字符串中的一种方法,各个编程语言都有各自的格式化字符串方法。对于 Python,官方文档中列举有四种,选择适合的即可。
f-string
通过在以 f""
包括的字符串中使用 {expression}
来嵌入值,在表达式后紧接 :
!
=
等格式说明符可以指定格式。重复的花括号可以得到花括号本身。详见官方文档。
>>> a = 11.4514
>>> f"{a}"
'11.4514'
>>> f"ww{a:08.2f}" # 格式化
'ww00011.45'
>>> f"{a=}" # 自说明
'a=11.4514'
>>> f"{int!r}" # 转换
"<class 'int'>"
>>> f"}}{a}{{" # 花括号转义
'}11.4514{'
2
3
4
5
6
7
8
9
10
11
format
format()
是字符串对象的一个方法[2]。同样通过花括号发挥效用。在格式说明符 :
!
上的语法与 f-string
相同,但其余部分略有不同。
>>> a = 11.4514
>>> "{}".format(a)
'11.4514'
>>> "{=}".format(a) # 没有`=`说明符
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: '='
>>> "{:08.2f}".format(a)
'00011.45'
>>> b = 191.981
>>> "{1:.2f} {0:.3f}".format(a, b) # 指定位置
'191.98 11.451'
>>> "{b:.2f} {a:.3f}".format(a=a, b=b) # 指定关键字
'191.98 11.451'
2
3
4
5
6
7
8
9
10
11
12
13
14
这意味着你可以在 format
方法中使用参数解包等很帅的操作 —— cool!
更多信息参见官方文档
旧式格式化
Python 支持通过在字符串后紧跟 %
符进行格式化。当只有一个值时括号可省。
又被称作 printf
风格格式化,详见官方文档。C 语言中的相关内容参见 cppreference。
>>> "%.2f" % (a)
'11.45'
2
手动格式化
详见官方文档